﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization;
using SystemTypes;
using NetSettings;

namespace Semantic
{
    [Serializable]
    public enum ErrorMsg {Ok , NoConsGoal, UndefinedQuestionType, UnnownTerm, ConcludeError, NetworkHole};

    [Serializable]
    public class UnnamedNode
    {
        public Guid ID;
        public List<Guid> InArcs;
        public List<Guid> OutArcs;

        public UnnamedNode()
        {
            ID = Guid.NewGuid();
            InArcs = new List<Guid>();
            OutArcs = new List<Guid>();
        }
        public UnnamedNode(List<Guid> inArcs, List<Guid> outArcs)
        {
            ID = Guid.NewGuid();
            InArcs = inArcs;
            OutArcs = outArcs;
        }
    }

    [Serializable]
    public class NamedNode : UnnamedNode
    {
        public string Name;
        public List<string> Synset;

        public NamedNode() { }
        public NamedNode(List<Guid> inArcs, List<Guid> outArcs) : base(inArcs, outArcs) { }
        public NamedNode(string nodeName) : base() { Name = nodeName; }
        public NamedNode(string nodeName, List<string> synset)
        { 
            Name = nodeName;
            Synset = synset;
        }
        public NamedNode(List<Guid> inArcs, List<Guid> outArcs, string nodeName, List<string> synset) : base(inArcs, outArcs)
        {
            Name = nodeName;
            Synset = synset;
        }
    }

    [Serializable]
    public class Arc
    {
        public Guid ID;
        public Guid OutNode;
        public Guid InNode;
        public Types.ArcType Type;

        public Arc() { ID = Guid.NewGuid(); }
        public Arc(Types.ArcType type)
        {
            ID = Guid.NewGuid();
            Type = type;
        }
        public Arc(Types.ArcType type, Guid outNode, Guid inNode)
        {
            ID = Guid.NewGuid();
            Type = type;
            OutNode = outNode;
            InNode = inNode;
        }
    }

    [Serializable]
    public class SemanticNetwork
    {
        public Guid ID;
        public List<Arc> Arcs;
        public List<UnnamedNode> Nodes;
        public List<Guid> IntersectionWay;

        public SemanticNetwork()
        {
            ID = Guid.NewGuid();
            Nodes = new List<UnnamedNode>();
            Arcs = new List<Arc>();
            IntersectionWay = new List<Guid>();
        }
        public SemanticNetwork(List<Arc> arcs, List<UnnamedNode> nodes)
        {
            ID = Guid.NewGuid();
            Arcs = arcs;
            Nodes = nodes;
            IntersectionWay = new List<Guid>();
        }

        public void AddNewNode(UnnamedNode node) { Nodes.Add(node); }
        public UnnamedNode GetNodeById(Guid nodeId)
        {
            return Nodes.FirstOrDefault(node => node.ID == nodeId);
        }

        public bool DeleteNode(Guid nodeId)
        {
            var node = GetNodeById(nodeId);
            foreach (var arcID in node.OutArcs)
                Arcs.Remove(GetArcById(arcID));
            foreach (var arcID in node.InArcs)
                Arcs.Remove(GetArcById(arcID));
            node.OutArcs.Clear();
            node.InArcs.Clear();
            return Nodes.Remove(GetNodeById(nodeId));
        }

        public void AddNewArc(Arc arc) { Arcs.Add(arc); }
        public Arc GetArcById(Guid arcId)
        {
            return Arcs.FirstOrDefault(arc => arc.ID == arcId);
        }

        public bool DeleteArc(Guid arcId)
        {
            return Arcs.Remove(GetArcById(arcId));
        }

        /// <summary>
        /// Один шаг метода пересечений
        /// </summary>
        /// <param name="node">Вершина, от которой ведётся построение сем сети</param>
        /// <param name="goalNodes">Вершины, которые должны быть включены в сем сеть</param>
        /// <returns>Id дуги, вошедшей в сем сеть</returns>
        public Guid ExpandNode(UnnamedNode node, List<UnnamedNode> goalNodes, List<Types.ArcType> supportedArcTypes)
        {
            if (node.OutArcs.Count == 0) return Guid.Empty;
            foreach (var outArc in node.OutArcs.Where(arc => supportedArcTypes.Contains(GetArcById(arc).Type)))
            {
                var arc = GetArcById(outArc);
                var inNode = GetNodeById(arc.InNode);
                if (goalNodes.Contains(inNode))
                {
                    IntersectionWay.Add(outArc);
                    return outArc;
                }
                else
                {
                    var id = ExpandNode(inNode, goalNodes, supportedArcTypes);
                    if (id != Guid.Empty)
                    {
                        IntersectionWay.Add(outArc);
                        return id;
                    }
                }
            }
            return Guid.Empty;
        }
     }

    [Serializable]
    public class Explanation
    {
        public List<Guid> Arcs;

        public Explanation() { Arcs = new List<Guid>(); }
        public Explanation(List<Guid> arcs) { Arcs = arcs; }

        public string ShowMessage(ErrorMsg type)
        {
            switch (type)
            {
                case ErrorMsg.Ok:return "";
                case ErrorMsg.ConcludeError:return "Ошибка вывода";
                case ErrorMsg.NetworkHole: return "Отсутствует семантическая сеть или её часть";
                case ErrorMsg.NoConsGoal: return "Нет цели консультации";
                case ErrorMsg.UndefinedQuestionType: return "Не определён тип вопроса";
                case ErrorMsg.UnnownTerm: return "Неизвестный термин";
                default:
                    return "";
            }
        }
    }

    [Serializable]
    public class Consultation
    {
        public Guid ID;
        public string Goal;
        public SemanticNetwork SemanticNet;

        public string Answer;
        public Explanation ExplainRoute;

        public Consultation()
        {
            ID = Guid.NewGuid();
            Goal = "";
        }
        public Consultation(string goal, SemanticNetwork net)
        {
            ID = Guid.NewGuid();
            Goal = goal;
            SemanticNet = net;
        }

        //имя вершины для начала вывода, если она одна
        [XmlIgnore] private string _target = "";
        //имена исходных вершин
        [XmlIgnore] private List<string> keywords = new List<string>();
        //ключевые слова-связки, используемые при ответе пользователю
        [XmlIgnore] private List<string> answerWords = new List<string>();
        //используемые типы дуг для текущего типа вопроса
        [XmlIgnore] private List<Types.ArcType> supportedArcTypes = new List<Types.ArcType>();
        /// <summary>
        /// Логический вывод
        /// </summary>
        /// <returns>0 если вывод сделан, иначе код ошибки</returns>
        /// код ошибки 1: Нет цели консультации
        /// код ошибки 2: Не определён тип вопроса
        /// код ошибки 3: Неизвестный термин
        /// код ошибки 41,42: Ошибка вывода
        /// код ошибки 51,52: Отсутствует семантическая сеть или её часть
        public ErrorMsg Conclude(Settings _Settings)
        {
            //инициализация
            ExplainRoute = new Explanation();
            if (Goal == "") return ErrorMsg.NoConsGoal;
            Goal = Goal.ToLower();
            var questionType = DefineQuestionType(Goal, _Settings);
            if (questionType == Types.QuestionTypes.Undefined) return ErrorMsg.UndefinedQuestionType;
            if (SemanticNet == null)
                return ErrorMsg.NetworkHole;
            return _target == "" ? IntersectionMethod(_Settings) : SimpleExpantion(_Settings);
        }

        /// <summary>
        /// Метод прямого распространения : в случае 1 исходной вершины
        /// </summary>
        /// <param name="_Settings">Настройки оболочки</param>
        /// <returns>0 если вывод сделан, иначе код ошибки</returns>
        public ErrorMsg SimpleExpantion(Settings _Settings)
        {
            var concludeVariantsISA = new List<UnnamedNode>();
            var concludeVariantsAPartOf = new List<UnnamedNode>();
            var concludeVariantsElementOf = new List<UnnamedNode>();
            var concludeVariantsSubtopic = new List<UnnamedNode>();
            var concludeVariantsPrecede = new List<UnnamedNode>();
            var concludeVariantsUse = new List<UnnamedNode>();
            var concludeVariantsRestriction = new List<UnnamedNode>();

            List<UnnamedNode> concludeNext;
            NamedNode finalNode = null;
            var restText = "";
            //нахождение в сем сети исходной именованной вершины
            var startNodeName = (from t in SemanticNet.Nodes
                                 where t.GetType() == typeof(NamedNode)
                                 where ((NamedNode)t).Name.ToLower() == _target.ToLower()
                                 select t as NamedNode).FirstOrDefault();
            if (startNodeName == null) return ErrorMsg.UnnownTerm;
            //нахождение соответствующей исходной неименнованной вершины
            var startNodeEmpty = SemanticNet.GetNodeById(SemanticNet.GetArcById(startNodeName.InArcs[0]).OutNode);
            ExplainRoute.Arcs.Add(SemanticNet.GetArcById(startNodeName.InArcs[0]).ID);
            if (startNodeEmpty == null)
                return ErrorMsg.NetworkHole;
            //все вершины на концах исходящих дуг, разбитые на группы по типам дуг
            if (supportedArcTypes.Contains(Types.ArcType.IsA))
                concludeVariantsISA.AddRange(CollectCloseNodesViaDefinedArcType(startNodeEmpty, Types.ArcType.IsA));
            if (supportedArcTypes.Contains(Types.ArcType.APartOf))
                concludeVariantsAPartOf.AddRange(CollectCloseNodesViaDefinedArcType(startNodeEmpty, Types.ArcType.APartOf));
            if (supportedArcTypes.Contains(Types.ArcType.ElementOf))
                concludeVariantsElementOf.AddRange(CollectCloseNodesViaDefinedArcType(startNodeEmpty, Types.ArcType.ElementOf));
            if (supportedArcTypes.Contains(Types.ArcType.Subtopic))
                concludeVariantsSubtopic.AddRange(CollectCloseNodesViaDefinedArcType(startNodeEmpty, Types.ArcType.Subtopic));
            if (supportedArcTypes.Contains(Types.ArcType.Precede))
                concludeVariantsPrecede.AddRange(CollectCloseNodesViaDefinedArcType(startNodeEmpty, Types.ArcType.Precede));
            if (supportedArcTypes.Contains(Types.ArcType.Use))
                concludeVariantsUse.AddRange(CollectCloseNodesViaDefinedArcType(startNodeEmpty, Types.ArcType.Use));
            concludeVariantsRestriction.AddRange(CollectCloseNodesViaDefinedArcType(startNodeEmpty, Types.ArcType.Restriction));
            //проверка на наличие ограничений
            if (concludeVariantsRestriction.Count > 0)
            {
                foreach (var currentArc in
                concludeVariantsRestriction[0].OutArcs.Select(outArcId => SemanticNet.GetArcById(outArcId)).Where(
                    currentArc => currentArc.Type == Types.ArcType.Name))
                    finalNode = SemanticNet.GetNodeById(currentArc.InNode) as NamedNode;
                if (finalNode != null) restText = "; Ограничение:" + finalNode.Name;
            }
            //проверка на изолированность вершины
            if (concludeVariantsISA.Count == 0 &&
                concludeVariantsAPartOf.Count == 0 &&
                concludeVariantsElementOf.Count == 0 &&
                concludeVariantsSubtopic.Count == 0 &&
                concludeVariantsPrecede.Count == 0 &&
                concludeVariantsUse.Count == 0)
            {
                Answer = _target + " это " + _target + restText;
                return ErrorMsg.Ok;
            }
            //выбор типа дуги для вывода
            var used = concludeVariantsISA.Count > 0
                           ? Types.ArcType.IsA
                           : (concludeVariantsAPartOf.Count > 0
                                  ? Types.ArcType.APartOf
                                  : (concludeVariantsElementOf.Count > 0
                                         ? Types.ArcType.ElementOf
                                         : (concludeVariantsSubtopic.Count > 0
                                                ? Types.ArcType.Subtopic
                                                : (concludeVariantsPrecede.Count > 0 ? Types.ArcType.Precede : Types.ArcType.Use))));
            var index = 0;
            if (answerWords.Count == supportedArcTypes.Count)
                index = supportedArcTypes.IndexOf(used);

            switch (used)
            {
                case Types.ArcType.IsA:
                    concludeNext = concludeVariantsISA;
                    break;
                case Types.ArcType.APartOf:
                    concludeNext = concludeVariantsAPartOf;
                    break;
                case Types.ArcType.ElementOf:
                    concludeNext = concludeVariantsElementOf;
                    break;
                case Types.ArcType.Subtopic:
                    concludeNext = concludeVariantsSubtopic;
                    break;
                case Types.ArcType.Precede:
                    concludeNext = concludeVariantsPrecede;
                    break;
                case Types.ArcType.Use:
                    concludeNext = concludeVariantsUse;
                    break;
                default:
                    return ErrorMsg.NetworkHole;
            }
            Answer = _target + ' ' + answerWords[index] + ' ';
            //переход по дуге к новой вершине
            foreach (var currentArc in
                concludeNext[0].OutArcs.Select(outArcId => SemanticNet.GetArcById(outArcId)).Where(
                    currentArc => currentArc.Type == Types.ArcType.Name))
            {
                finalNode = SemanticNet.GetNodeById(currentArc.InNode) as NamedNode;
                ExplainRoute.Arcs.Add(currentArc.ID);
            }
            if (finalNode == null) return ErrorMsg.NetworkHole;
            Answer += finalNode.Name + restText;
            return ErrorMsg.Ok;
        }

        /// <summary>
        /// Метод пересечений : в случае нескольких исходных вершин
        /// </summary>
        /// <param name="_Settings">Настройки оболочки</param>
        /// <returns>0 если вывод сделан, иначе код ошибки</returns>
        public ErrorMsg IntersectionMethod(Settings _Settings)
        {
            var concludeVariantsRestriction = new List<UnnamedNode>();
            NamedNode finalNode = null;
            var restText = "";
            //нахождение в сем сети исходных именованных вершин
            var startNodes = keywords.Select(keyword => (from t in SemanticNet.Nodes
                                                         where t.GetType() == typeof (NamedNode)
                                                         where ((NamedNode) t).Name.ToLower() == keyword.ToLower()
                                                         select t as NamedNode).FirstOrDefault()).ToList();
            if (startNodes.Count != keywords.Count) return ErrorMsg.UnnownTerm;
            var startNodeName = startNodes[0];
            //исходная неименованная вершина
            var startNodeEmpty = SemanticNet.GetNodeById(SemanticNet.GetArcById(startNodeName.InArcs[0]).OutNode);
            ExplainRoute.Arcs.Add(SemanticNet.GetArcById(startNodeName.InArcs[0]).ID);
            concludeVariantsRestriction.AddRange(CollectCloseNodesViaDefinedArcType(startNodeEmpty, Types.ArcType.Restriction));
            //вершины, которые должны попасть в пересечение
            var goalNodes = new List<UnnamedNode>();
            for (var i = 1; i < startNodes.Count; i++)
                goalNodes.Add(SemanticNet.GetNodeById(SemanticNet.GetArcById(startNodes[i].InArcs[0]).OutNode));

            //проверка на наличие ограничений
            if (concludeVariantsRestriction.Count > 0)
            {
                foreach (var currentArc in
                concludeVariantsRestriction[0].OutArcs.Select(outArcId => SemanticNet.GetArcById(outArcId)).Where(
                    currentArc => currentArc.Type == Types.ArcType.Name))
                    finalNode = SemanticNet.GetNodeById(currentArc.InNode) as NamedNode;
                if (finalNode != null) restText = "; Ограничение:" + finalNode.Name;
            }

            var firstArc = SemanticNet.ExpandNode(startNodeEmpty, goalNodes, supportedArcTypes);
            if(firstArc == Guid.Empty)
            {
                Answer = startNodeName.Name + " это " + startNodeName.Name + restText;
                return ErrorMsg.Ok;
            }
            else
            {
                var arc = SemanticNet.GetArcById(firstArc);
                if(supportedArcTypes.Contains(arc.Type))
                {
                    var index = supportedArcTypes.IndexOf(arc.Type);
                    ExplainRoute.Arcs.Add(firstArc);
                    ExplainRoute.Arcs.AddRange(SemanticNet.IntersectionWay);
                    var node = SemanticNet.GetNodeById(arc.InNode);
                    var nameArc =
                        node.OutArcs.Where(thearc => SemanticNet.GetArcById(thearc).Type == Types.ArcType.Name).First();
                    var realNameArc = SemanticNet.GetArcById(nameArc);
                    finalNode = SemanticNet.GetNodeById(realNameArc.InNode) as NamedNode;
                    ExplainRoute.Arcs.Add(realNameArc.ID);
                    if (finalNode == null) return ErrorMsg.NetworkHole;
                    Answer = startNodeName.Name + ' ' + answerWords[index] + ' ' + finalNode.Name + restText;
                }
                else
                {
                    Answer = startNodeName.Name + " это " + startNodeName.Name + restText;
                    return ErrorMsg.Ok;
                }
            }
            return ErrorMsg.Ok;
        }
        /// <summary>
        /// Найти все вершины, расположенные на концах исходящих из начальной дуг определённого типа
        /// </summary>
        /// <param name="startNode">начальная вершина</param>
        /// <param name="arcType">тип исходящих дуг</param>
        /// <returns>список вершин, расположенных на концах дуг</returns>
        public IEnumerable<UnnamedNode> CollectCloseNodesViaDefinedArcType(UnnamedNode startNode, Types.ArcType arcType)
        {
            var res= (from outArcId in startNode.OutArcs
                    select SemanticNet.GetArcById(outArcId)
                    into currentArc
                    where currentArc.Type == arcType
                    select SemanticNet.GetNodeById(currentArc.InNode));
            ExplainRoute.Arcs.AddRange(
                from outArcId in startNode.OutArcs
                where SemanticNet.GetArcById(outArcId).Type == arcType
                select outArcId);
            return res;
        }

        /// <summary>
        /// Определить тип заданного вопроса
        /// </summary>
        /// <param name="question">Вопрос</param>
        /// <param name="settings">Настройки</param>
        /// <returns>Тип вопроса</returns>
        public Types.QuestionTypes DefineQuestionType(string question, Settings settings)
        {
            var qtype = Types.QuestionTypes.Undefined;
            var wordsInQRegex = new List<string>();
            var wordsInPRegex = new List<string>();
            var qseparators = new char[] {' ', '?'};
            var pseparators = new char[] {' ', '.', '\\', '[', ']', '|', '{', '}', '+', '*', '?', '(', ')'};
            var aseparators = new char[] {'.', '\\', '[', ']', '|', '{', '}', '+', '*', '?', '(', ')' };

            foreach (var pattern in
                settings.QuestionsInfo.QuestionsData.Where(pattern => settings.Validate(pattern.QuestionPattern, question)))
            {
                qtype = pattern.QuestionType;
                wordsInQRegex = pattern.QuestionPattern.Split(pseparators).ToList();
                wordsInPRegex = pattern.AnswerPattern.Split(aseparators).ToList();
                supportedArcTypes = pattern.SupportedArcs;
                break;
            }
            if (qtype == Types.QuestionTypes.Undefined) return qtype;

            var wordsInQuestion = question.Split(qseparators).ToList();
            var keys = wordsInQuestion.Where(word => !wordsInQRegex.Contains(word)).ToList();
            var toStick = new bool[wordsInQuestion.Count];
            var unitedKeys = new List<string>();
            toStick[0] = false;
            for (var i = 1; i < wordsInQuestion.Count; i++)
                if(keys.Contains(wordsInQuestion[i]) && keys.Contains(wordsInQuestion[i - 1]))
                    toStick[i] = true;
            for (var i = wordsInQuestion.Count - 1; i >= 0; i--)
                if (toStick[i])
                {
                    wordsInQuestion[i - 1] += ' ' + wordsInQuestion[i];
                    wordsInQuestion[i] = "";
                }
                else
                    if (i != wordsInQuestion.Count - 1)
                        if (toStick[i + 1])
                            unitedKeys.Add(wordsInQuestion[i]);

            keys = wordsInQuestion.Where(word => !wordsInQRegex.Contains(word)).ToList();
            //keys.AddRange(unitedKeys);
            switch (keys.Count)
            {
                case 0:
                    break;
                case 1:
                    _target = keys[0];
                    break;
                default:
                    keywords = keys;
                    break;
            }
            foreach (var word in wordsInPRegex.Where(word => word.Trim() != ""))
                answerWords.Add(word.Trim());
            return qtype;
        }
    }
}
